/* -*- Mode: C++; indent-tabs-mode: nil; tab-width: 4 -*-
 * -*- coding: utf-8 -*-
 *
 * Copyright (C) 2020 KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "pulseaudiomanager.h"
#include <QDebug>

#define VOLMUE_NORMAL 100.0f

#define MIN_VOLUME PA_VOLUME_NORM/500

PulseAudioManager* PulseAudioManager::s_pulseAudioManager = nullptr;

PulseAudioManager::PulseAudioManager(QObject *parent) : QObject(parent)
{
    connectPulseContext();
    s_pulseAudioManager = this;
}

PulseAudioManager::~PulseAudioManager()
{
    if(m_paMainloopApi){
        m_paMainloopApi->quit(m_paMainloopApi, 0);
        m_paMainloopApi = nullptr;
    }
    if(m_paContext){
        pa_context_set_state_callback(m_paContext, nullptr, nullptr);
        pa_context_disconnect(m_paContext);
        pa_context_unref(m_paContext);
        m_paContext = nullptr;
    }
    if(m_paThreadMainLoop){
        pa_threaded_mainloop_stop(m_paThreadMainLoop);
        pa_threaded_mainloop_free(m_paThreadMainLoop);
        m_paThreadMainLoop = nullptr;
    }
}

void PulseAudioManager::serverInfoCallback(pa_context *c, const pa_server_info *i, void *data)
{
    if(c) {
        if(s_pulseAudioManager->m_defaultSinkName != i->default_sink_name){
            s_pulseAudioManager->m_defaultSinkName = i->default_sink_name;

        }
        if(s_pulseAudioManager->m_defaultSourceName != i->default_source_name){
            s_pulseAudioManager->m_defaultSourceName = i->default_source_name;
        }
        pa_context_get_sink_info_by_name(c,s_pulseAudioManager->m_defaultSinkName,sinkInfoCallback,data);
        pa_context_get_source_info_by_name(c,s_pulseAudioManager->m_defaultSourceName,sourceInfoCallback,data);
    }
}

void PulseAudioManager::subscribeCallback(pa_context *c, pa_subscription_event_type_t type, uint32_t idx, void *data)
{
    switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
        case PA_SUBSCRIPTION_EVENT_SINK:
        case PA_SUBSCRIPTION_EVENT_SOURCE:
            pa_operation *o;
            if (!(o = pa_context_get_server_info(c,serverInfoCallback,data))) {
                USD_LOG(LOG_WARNING,"pa_operation : get server info faild");
                return;
            }
            pa_operation_unref(o);
            break;
        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
            break;
        default:
            break;
       }
}

void PulseAudioManager::contextStateCallback(pa_context *c, void *data)
{
    assert(c && data);
    switch (pa_context_get_state(c)) {
    case PA_CONTEXT_READY:
        USD_LOG(LOG_DEBUG, "PA_CONTEXT_READY");
        pa_context_get_server_info(c,serverInfoCallback,data);
        pa_context_set_subscribe_callback(c, subscribeCallback, data);
        pa_context_subscribe(c, pa_subscription_mask_t( PA_SUBSCRIPTION_MASK_SINK  |
                                                        PA_SUBSCRIPTION_MASK_SOURCE), nullptr, nullptr);//订阅pa_context 事件
        break;
    case PA_CONTEXT_FAILED:
        USD_LOG(LOG_WARNING, "PA_CONTEXT_FAILED");
        break;
    case PA_CONTEXT_TERMINATED:
        USD_LOG(LOG_WARNING, "PA_CONTEXT_TERMINATED");
        break;
    default:
        break;
    }
}

void PulseAudioManager::connectPulseContext()
{
    if(!(m_paThreadMainLoop = pa_threaded_mainloop_new())){
        USD_LOG(LOG_WARNING,"new m_paThreadMainLoop faild");
        return;
    }

    m_paMainloopApi = pa_threaded_mainloop_get_api(m_paThreadMainLoop);
    if(!m_paMainloopApi){
        USD_LOG(LOG_WARNING,"get m_paMainloopApi faild");
        return;
    }

    if(!(m_paContext = pa_context_new(m_paMainloopApi, nullptr))){
        USD_LOG(LOG_WARNING,"new m_paContext faild");
    }

    if(pa_context_connect(m_paContext, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0){
        qDebug()<<"pa_context_connect";
        USD_LOG(LOG_WARNING,"connect pa_context faild");
    }
    pa_context_set_state_callback(m_paContext, contextStateCallback, m_paMainloopApi);
    if(pa_threaded_mainloop_start(m_paThreadMainLoop) < 0){
        USD_LOG(LOG_WARNING,"pa_threaded_mainloop_start faild");
    }
}

void PulseAudioManager::sinkInfoCallback(pa_context *c, const pa_sink_info *si, int eol, void *data)
{
    s_pulseAudioManager->updateSinkInfo(si);
}

void PulseAudioManager::sourceInfoCallback(pa_context *c, const pa_source_info *so, int eol, void *data)
{
    s_pulseAudioManager->updateSourceInfo(so);
}

void PulseAudioManager::updateSinkInfo(const pa_sink_info* si)
{
    if(si){
        if(m_realSinkVolume != pa_cvolume_max(&(si->volume))){
            m_realSinkVolume = pa_cvolume_max(&(si->volume));
            m_sinkVolume = volForPulseVol(pa_cvolume_max(&(si->volume)));
            Q_EMIT sinkVolumeChanged(m_sinkVolume);
        }
        if(m_sinkMute != si->mute){
            m_sinkMute = si->mute;
            Q_EMIT sinkMuteChanged(m_sinkMute);
        }
        m_paSinkVolume = si->volume;
        m_channelMap = si->channel_map;
        m_balance = pa_cvolume_get_balance(&si->volume,&si->channel_map);
    }
}

void PulseAudioManager::updateSourceInfo(const pa_source_info* so)
{
    if(so){
        if(m_sourceMute != so->mute){
            m_sourceMute = so->mute;
        }
    }
}

uint32_t PulseAudioManager::volToPulseVol(int value)
{
    return value == 0 ? MIN_VOLUME : value / VOLMUE_NORMAL * PA_VOLUME_NORM;
}

uint32_t PulseAudioManager::volForPulseVol(int value)
{
    return qRound(value * VOLMUE_NORMAL / PA_VOLUME_NORM) ;
}

void PulseAudioManager::setSinkVolume(int value)
{
    if(m_sinkVolume == value){
        return;
    }
    pa_operation* o;
    pa_cvolume volmue;
    volmue.channels = m_paSinkVolume.channels;
    int paValue = volToPulseVol(value);
    for(int i = 0; i < m_paSinkVolume.channels ; ++i){
        volmue.values[i] = paValue;
    }
    if(m_balance != 0) {
        pa_cvolume_set_balance(&volmue,&m_channelMap,m_balance);
    }
    if(!(o = pa_context_set_sink_volume_by_name(m_paContext, m_defaultSinkName, &volmue, nullptr, nullptr))) {
        USD_LOG(LOG_WARNING,"pa_operation : set sink volume faild");
        return;
    }
    pa_operation_unref(o);
}

void PulseAudioManager::setSinkMute(bool mute)
{
    if(m_sinkMute == mute){
        return;
    }
    pa_operation* o;
    if(!(o = pa_context_set_sink_mute_by_name(m_paContext, m_defaultSinkName, mute, nullptr, nullptr))) {
        USD_LOG(LOG_WARNING,"pa_operation : set sink mute faild");
        return;
    }
    pa_operation_unref(o);
}

void PulseAudioManager::setSourceMute(bool mute)
{
    if(m_sourceMute == mute){
        return;
    }
    pa_operation* o;
    if(!(o = pa_context_set_source_mute_by_name(m_paContext, m_defaultSourceName, mute, nullptr, nullptr))) {
        USD_LOG(LOG_WARNING,"pa_operation : set source mute faild");
        return;
    }
    pa_operation_unref(o);
}

